package org.beynet.jsonbinding.bind;

import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

public class JSONBContext {
    
    /**
     * construct the context object
     * 
     * @param classesToBeBound
     * @throws IllegalArgumentException
     */
    private JSONBContext(Class<?>... classesToBeBound) throws IllegalArgumentException {
        if (classesToBeBound == null || classesToBeBound.length == 0)
            throw new IllegalArgumentException("classes to be bound must not be null nor empty");
        classesInScope = new HashMap<>();
        classesInScopePerName = new HashMap<>();
        for (Class<?> cl : classesToBeBound) {
            if (!cl.isAnnotationPresent(JSONElement.class)) {
                throw new IllegalArgumentException("the class " + cl.getName() + " is not annotated with " + JSONElement.class.getName());
            }
            JSONElement annotation = cl.getAnnotation(JSONElement.class);
            if (annotation.name()==null) {
                throw new IllegalArgumentException("class "+cl.getCanonicalName()+" annotated whith "+JSONElement.class+" but no name is provided  - this is required when annoting a class");
            }
            readClass(cl,annotation.name());
        }
    }

    /**
     * read current class
     * 
     * @param cl
     */
    private void readClass(Class<?> cl,String name) {
        JSONBClassStructure st = new JSONBClassStructure(this, cl);
        addClassIfNotAlreadyInScope(cl, st);
        classesInScopePerName.put(name, st);
    }

    /**
     * @param classesToBeBound
     * @return
     * @throws IllegalArgumentException
     *             : if
     *             <ul>
     *             <li>classesToBeBound is null or empty</li>
     *             <li>one class is not annotated by {@link JSONElement}
     *             </ul>
     */
    public static JSONBContext newInstance(Class<?>... classesToBeBound) throws IllegalArgumentException {
        return new JSONBContext(classesToBeBound);
    }

    /**
     * @param cl : the the structure of the given class.
     * @return
     */
    JSONBClassStructure getClassInScopeStructure(Class<?> cl) {
        synchronized (classesInScope) {
            return classesInScope.get(cl);
        }
    }
    
    JSONBClassStructure getClassStructureByRootName(String name) {
        return classesInScopePerName.get(name);
    }
    
    boolean isClassesInScope(Class<?> cl) {
        synchronized (classesInScope) {
            return classesInScope.get(cl) != null;
        }
    }

    /**
     * @param cl
     * @param st
     */
    public void addClassIfNotAlreadyInScope(Class<?> cl, JSONBClassStructure st) {
        synchronized (classesInScope) {
            if (classesInScope.get(cl) != null) {
                return;
            }
            classesInScope.put(cl, st);
            if (logger.isDebugEnabled()) {
                logger.debug("Class " + cl + " added in scope");
            }
        }
    }
    
    public JSONBMarshaller createMarshaller() {
        return new JSONBMarshaller(this,"UTF-8");
    }
    
    public JSONBMarshaller createMarshaller(String targetEncoding) {
        return new JSONBMarshaller(this,targetEncoding);
    }
    
    public JSONBUnmarshaller createUnmarshaller() {
        return new JSONBUnmarshaller(this);
    }

    private Map<Class<?>, JSONBClassStructure> classesInScope;
    private Map<String, JSONBClassStructure> classesInScopePerName;
    private final static Logger logger = Logger.getLogger(JSONBContext.class);

}
